<template>
  <div id="colosseum" class="tetris-wrapper">
    <div class="game-wrapper" ref="container">
      <div class="block-group" v-for="(item, index) in structureArray" :key="index" >
        <div class="block" v-for="(block, blockIndex) in item" :key="blockIndex" 
        :class="{
            black: block.state === 1,
            white: block.state === 2,
            red: block.state === 3,
            blue: block.state === 4,
            green: block.state === 5,
            yellow: block.state === 6,
            cyan: block.state === 7,
            magenta: block.state === 8,
            oxy: block.state === 9,
          }"
        ></div>
      </div>
    </div>
    <MySQL ref="db" :record="record"/>
  </div>
</template>

<script>
import MySQL from '@/components/MySQL.vue'

export default {
  name: 'Colosseum',
  components: {
    MySQL,
  },
  props: {
    sign: String, // 游戏局标识
    me: String, // 玩家自己的名字
    space: String, // 空间名
    spaceId: String, // 空间ID
    serial: Number, // 序号
    roles: Array, // 角色列表
    state: Number, // 自己宠物的颜色
    watch: Object, // 看板信息
  },
  data () {
    return {
      foodInterval: 2048, // 食物掉落时间间隔
      interval: 300, // 定时间隔时间
      powerInterval: 150, // 加速后的时间间隔
      frameInterval: 66, // 帧发送间隔
      frameTimer: null, // 帧同步定时器
      transverse: 56, // 横坐标方格数量
      longitudinal: 30, // 纵坐标方格数量
      structureArray: [], // 游戏画布结构数组
      projection: {
        msgList: [], // 消息列表
        recordList: [], // 成绩列表
      }, // 看板，用于同步数据到母级看板
      dragon: {}, // 角色信息
      isSee: false, // 渲染尘埃时,如果发现尘埃和自己重叠,认为自己吃到了,用此属性做标记
      seeSign: null, // 被吃到的尘埃的sign
      seedSigns: [], // 被吃到的所有尘埃
      tetris: {
        name: 'tetris', // 掉落方块的名字
        size: 1, // 掉落方块的大小
        state: 1, // 可选,方块的颜色,黑色
        timer: null, // 存储定时器
        moveDirection: 2, // 移动方向，0、1、2、3，上右下左
        nextMoveDirection: 2, // 将要移动的方向，0、1、2、3，上右下左
        head: {}, // 方块头坐标
        tail: {}, // 方块头坐标
      }, //方块
      record: {
        id: this.sign, // 记录的唯一标识，方便更新
        username: this.me, // 用户名
        space_name: this.space, // 区间名
        times_kill: 0, // = 击杀次数
        times_killed: 0, // 被击杀次数
        size: 3, // 分数
        create_time: this.now, // 创建时间
        update_time: null, // 更新时间
      }, // 成绩数据
    }
  },
  methods: {
    // 保存成绩数据
    save (){
      this.$refs.db.save();
    },
    // 向ws发送消息
    send(msg){
      this.$parent.send(msg)
    },
    // 接收到ws发来的消息
    receive(data){
      let user = data.username
      let snake = this.dragon[user]
      if(!snake){
        console.log('没有找到此用户的宠物：' + user)
      }
      let order = data.order
      switch(order.type){
        case 'dead':
          this.deathTreatment(snake, order.cmd.by);
          this.updateWatch(user,order)
          break;
        case 'drop':
          this.generateDust(order.cmd);
          break;
        case 'render':
          this.renderThings(order.cmd);
          break;
        case 'see':
          this.updateWatch(user, order)
          break;
        default:
          console.log('不认识的指令：' + order.type);
          break;
      }
    },
    // 发送死亡的指令
    sendDeadOrder(by){
      let data = {
        order:{
          type: 'dead',
          cmd: {
            by: by, // 被谁击杀
          }
        }
      }
      this.send(JSON.stringify(data))
    },
    // 发送吃到食物的命令
    sendSeeOrder(sign){
      let data = {
        order:{
          type: 'see',
          cmd: {
            sign: sign, // 吃到了哪个
          }
        }
      }
      this.send(JSON.stringify(data))
    },
    // 发送自己的画面信息到服务器
    sendMyImage(){
      let snake = this.snake;
      this.sendImage(snake)
    },
    // 发送自己的画面信息到服务器
    sendImage(thing){
      thing.points = this.getPoints(thing);
      let data = {
        order:{
          type: 'image',
          cmd: thing
        }
      }
      this.send(JSON.stringify(data))
    },
    // 改变蛇的运动方向
    changeMoveDirection(direction) {
      let snake = this.snake;
      let judge = direction - snake.moveDirection; // 判断非法的蛇头转向
      if (judge === 2 || judge === -2) return;
      snake.nextMoveDirection = direction;
    },
    moveRule(thing) {
      let needInitSee = false;
      if(this.isSee){
        needInitSee = true;
      }
      // 蛇的移动规则定义
      let { x, y } = thing.head; // 当前蛇头的坐标
      let headPoint = this.structureArray[y][x]; // 当前蛇头的网格
      let headCode = headPoint.code; // 获取当前蛇头的code,用来生成新蛇头时++使用
      let newHeadState = 3; // 新的头部位置状态
      headPoint.state = thing.state; // 旧的头部位置状态,改为身体的颜色
      switch (
        thing.nextMoveDirection // 移动方向，计算移动后的横纵坐标
      ) {
        case 0:
          y--;
          break;
        case 1:
          x++;
          break;
        case 2:
          y++;
          break;
        case 3:
          x--;
          break;
        default:
          throw new Error("蛇的移动规则判断出错");
      }
      let oldPoint = this.structureArray[y][x];
      let sign = oldPoint.sign || this.seeSign;
      let got = (this.isSee || oldPoint.state == 1) && !this.seedSigns.includes(sign);
      let newHeadCode;
      if (got){
        this.seedSigns.push(sign);
        this.sendSeeOrder(sign);
        newHeadCode = ++headCode;
      }else {
        let points = this.getPoints(thing);
        newHeadCode = headCode;
        for (let point of points){
          if (![3].includes(point.state)){
            point.code = --point.code
          }
        }
      }
      this.structureArray[y].splice(x, 1, { state: newHeadState, code: newHeadCode, name: thing.name }); // 生成新的蛇头
      thing.head = { x, y };
      thing.moveDirection = thing.nextMoveDirection;
      // 只判断自己是否吃到食物（蛇头和食物坐标是否重合）
      this.whetherSeeDust(thing, got); 
      if(this.isSee && needInitSee){
        // 初始化isSee
        this.isSee = false;
        this.seeSign = null;
      }
    },
    // 根据是否吃到食物做不同的处理
    whetherSeeDust(thing, got) {
      if (got) {
        // 吃到了
        this.integralRule(thing); // 走积分
      } else if(thing.name !== this.tetris.name){
        // 没吃到食物，并且不是方块，重新生成蛇尾
        this.generateTail(thing); 
      }
    },
    generateDust(x) {
      // 生成食物,生成随机食物坐标
      // let x = Math.floor(Math.random() * this.transverse); // 生成随机数
      let y = 0; // 从顶部生成
      if (this.structureArray[y][x].state === 0) {
        // 是空位置
        this.tetris.head = { x, y }; // 新的食物坐标
        this.tetris.tail = { x, y }; // 新的食物坐标
        this.structureArray[y].splice(x, 1, { state: 1, code: Infinity, name: this.tetris.name }); // 修改坐标状态值,code无限大用来计算蛇尾时不会出错
        this.initTimer(this.tetris,this.foodInterval); // 食物下降定时器
      } else {
        // 位置不为空，重新生成
        this.generateDust(x);
      }
    },
    integralRule(snake) {
      // 积分规则
      snake.size ++;
      if (snake.name === this.me){
        let record = this.record;
        record.size = snake.size;
        record.update_time = this.now;
        this.save();
      }
    },
    generateTail(thing) {
      // 重新生成蛇尾(在没有吃到食物的情况下调用)
      let { x, y } = thing.tail; // 获取蛇尾坐标
      // 销毁之前的蛇尾(若当前蛇尾蛇尾位置是蛇头，则不做处理，因为头先生成,目前没有长度为1的蛇,故这里一直是true)
      if (this.structureArray[y][x].state !== 3)
        this.structureArray[y].splice(x, 1, { state: 0 });
      // 取上下左右code最小的是新蛇尾
      let point;
      let top =
        y - 1 >= 0 && (point = this.structureArray[y - 1][x]) && this.isEffectivePoint(thing, point)
          ? point.code
          : Infinity;
      let bottom =
        y + 1 < this.longitudinal && (point = this.structureArray[y + 1][x]) && this.isEffectivePoint(thing, point)
          ? point.code
          : Infinity;
      let left =
        x - 1 >= 0 && (point = this.structureArray[y][x - 1]) && this.isEffectivePoint(thing, point)
          ? point.code
          : Infinity;
      let right =
        x + 1 < this.transverse && (point = this.structureArray[y][x + 1]) && this.isEffectivePoint(thing, point)
          ? point.code
          : Infinity;
      let min = Math.min(top, bottom, left, right);
      switch (min) {
        case top:
          y--;
          break;
        case bottom:
          y++;
          break;
        case left:
          x--;
          break;
        case right:
          x++;
          break;
        default:
          console.log("重新生成蛇尾出错了");
          break;
      }
      thing.tail = { x, y }; // 保存蛇尾坐标
      this.structureArray[y].splice(x, 1, {state: 4, code: 0, name: thing.name}); // 将此坐标定义为蛇尾      
    },
    // 判断点位是否有效
    isEffectivePoint(thing, point){
      return point.name == thing.name && !!point.state
    },
    deathRule(thing) {
      let result = {
        by: thing.name, // 被谁杀死,默认是自己,即撞墙等自杀,无实际意义
        alive: false, // 是否存活
      };
      // 蛇的死亡规则判断
      let { x, y } = thing.head;
      let point;
      switch (
        thing.nextMoveDirection // 移动方向(碰壁判断和碰蛇身判断)
      ) {
        case 0: // 上
          if (y - 1 < 0) return result;
          point = this.structureArray[y - 1][x];
          break;
        case 1: // 右
          if (x + 1 > this.transverse - 1) return result;
          point = this.structureArray[y][x + 1];
          break;
        case 2: // 下
          if (y + 1 > this.longitudinal - 1) return result;
          point = this.structureArray[y + 1][x];
          break;
        case 3: // 左
          if (x - 1 < 0) return result;
          point = this.structureArray[y][x - 1];
          break;
        default:
          throw new Error("蛇的死亡规则判断错误");
      }
      result.by = point.name; // 被谁杀死
      if(point.state == 2){
        // 触碰禁忌的皆会消亡
        return result;
      }else if(thing.state == 1 && point.state != 0){
        // 禁忌不可知--食物触碰到物体便会变异,目前这个判断不会被触发,因为方块的逻辑已经迁移到了服务器
        return result;
      }else if (point.name != thing.name && [3, 4, 5, 6, 7, 8, 9].includes(point.state)){
        // 触碰到的点是是蛇,非蛇尾(正常来说,如果下一个位置是别人的蛇尾,是碰不到的,因为蛇是会动的,加速的情况可能会碰到,而且各个客户端的计时起点可能会差个几毫秒,这里就认为碰到蛇尾也会死)
        return result;
      }else if (point.name == thing.name && [5, 6, 7, 8, 9].includes(point.state)){
        // 触碰到的点是自己的躯干
        return result;
      } 
      result.alive = true;
      return result;
    },
    deathTreatment(thing, by) {
      // 蛇的死亡处理
      this.clearTimer(thing);
      this.clearFrameTimer();
      if (by == this.me){
        // 如果是被自己击杀，则击杀次数加1
        let record = this.record;
        record.times_kill ++
        record.update_time = this.now;
        this.save()
      }
      // 蛇尾变成食物
      if (thing.name == this.me){
        let { x , y } = thing.tail;
        this.clearSnake(thing);
        this.structureArray[y].splice(x, 1, {state: 1, name: 'siusn'});
        let siusn = {state: 1, name: 'siusn'};
        this.sendImage(siusn);
        this.clearThing(siusn);
      }
      console.log("死亡！！！！");
    },
    clearSnake(snake){
      // 蛇死亡后清除蛇身
      this.clearThing(snake);
    },
    clearThing(thing){
      // 清除图像
      for(let i = 0; i < this.longitudinal; i++){
        for(let j = 0; j < this.transverse; j++){
          let point = this.structureArray[i][j];
          if(point.name === thing.name){
            this.structureArray[i].splice(j, 1, {state: 0})
          }
        }
      }
    },
    // 清除旧的已失效的图像,thing为服务器传过来的新的点位信息
    clearInvalidThing(thing){
      let newPoints = thing.points;
      for(let i = 0; i < this.longitudinal; i++){
        lx:for(let j = 0; j < this.transverse; j++){
          let point = this.structureArray[i][j];
          if(point.name === thing.name){
            for(let newPoint of newPoints){
              if(j == newPoint.x && i == newPoint.y && point.state == newPoint.state){
                continue lx;
              }
            }
            this.structureArray[i].splice(j, 1, {state: 0})
          }
        }
      }
    },
    getPoints(thing){
      // 获取thing的所有点
      let points = [];
      for(let i = 0; i < this.longitudinal; i++){
        for(let j = 0; j < this.transverse; j++){
          let point = this.structureArray[i][j];
          if(point.name === thing.name){
            point.x = j;
            point.y = i;
            points.push(point);
          }
        }
      }
      return points;
    },
    initDragon() {
      // 初始化宠物数据
      this.dragon = {};
      let state = 5; //设置第一个宠物身体的颜色
      for(let role of this.roles){
        if(role == this.me){
          this.$emit("update:state", state);
        }
        this.dragon[role] = {
          name: role,
          size: 3, // 初始长度
          state: state, // 身体的颜色状态
          timer: null, // 存储定时器
          moveDirection: 1, // 蛇的移动方向，0、1、2、3，上右下左
          nextMoveDirection: 1, // 将要移动的方向，0、1、2、3，上右下左
          head: {}, // 蛇头的坐标
          tail: {}, // 蛇尾坐标
        }
        this.projection.recordList.push({
            username: role, // 用户名
            space_name: this.space, // 组名
            space_id: this.spaceId, //组ID
            times_kill: 0, // = 击杀次数
            times_killed: 0, // 被击杀次数
            size: 3, // 分数
        });
        state ++;
      }
      // 同步数据到父页面
      this.$emit("update:watch", this.projection);
    },
    initGrid() {
      // 初始化网格
      this.structureArray = [];
      for (let i = 0; i < this.longitudinal; i++) {
        this.structureArray.push([]);
        for (let j = 0; j < this.transverse; j++) {
          this.structureArray[i].push({
            state: 0, // 0为空，1为蛇身，2为蛇头，3为蛇尾，4为食物
          });
        }
      }
    },
    renderThings(things) {
      // 渲染
      for(let thing of things){
        if(thing.name == this.me){
          // 是自己的话就不需要渲染
          continue;
        }
        let points = thing.points;
        for(let point of points){
          let oldPoint = this.structureArray[point.y][point.x];
          if (oldPoint.name == this.me && thing.name == this.tetris.name){
            // 如果是尘埃和自己的位置重叠,则认为尘埃被自己吃到了
            let snake = this.snake;
            let { x, y } = snake.head;
            if(snake.nextMoveDirection == 0 && y >=point.y && x == point.x && snake.size - oldPoint.code < 4){
              this.isSee = true;
              this.seeSign = point.sign;
            }
            continue;
          }
          if (oldPoint.name == this.me || (thing.name == this.tetris.name && oldPoint.state != 0)){
            // 如果被渲染的位置刚好与自己重叠,或者旧的位置不是空,并且刚将要被渲染的是尘埃,则先不渲染,主要场景是防止吃到的食物再被刷新回来
            continue;
          }
          // 尘埃的code目前是没有值的,暂不需要,需要的时候再加
          this.structureArray[point.y].splice(point.x, 1, { state: point.state, code: point.code, name: thing.name, sign: point.sign });
        }
        this.clearInvalidThing(thing);
      }
    },
    // 更新看板信息
    updateWatch(user, order) {
      let type = order.type;
      let by = order.cmd.by;
      let msgList = this.projection.msgList;
      let recordList = this.projection.recordList;
      let num = msgList.length - 5; // 最多显示六条消息，而且还要插入一条，所以将列表减少到五条
      for (let i = 1; i <= num; i++){
        msgList.pop();
      }
      let msg = user;
      switch(type){
        case 'dead':
          if (!by || by.trim() == '' || by == 'god' || by == user){
            msg += " 自杀了";
          }else {
            msg += " 被 " + by + " 杀死了"
          }
          for (let record of recordList){
            if (record.username == by || (record.username == user && (!by || by.trim() == '' || by == 'god' || by == user))){
              record.times_kill ++;
              if (record.username == this.me){
                this.saveRecord(record);
              }
            }
          }
          break;
        case 'see':
          msg += " 吃到了食物";
          for (let record of recordList){
            if (record.username == user){
              record.size ++;
              if (record.username == this.me){
                this.saveRecord(record);
              }
            }
          }
          break;
        default:
          return;
      }
      // 将新消息放入头部
      msgList.unshift(msg);
      // 同步数据到父页面
      this.$emit("update:watch", this.projection);
    },
    // 保存战绩到服务器
    saveRecord(record) {
      record.id = this.sign;
      this.axios
      .post("http://" + window.location.hostname + ":14000/cola/record_save/", record)
      .then((res) => {
        let result = res.data;
        console.log(result);
        if (result.code == 0) {
          console.log(result.msg);
        } else {
          console.log(result.msg);
        }
      });
    },
    initSnake() {
      // 初始化蛇
      let i = 0;
      for(let user in this.dragon){
        let snake = this.dragon[user];
        this.structureArray[4 + i].splice(4, 1, { state: 3, code: 2, name: snake.name });
        this.structureArray[4 + i].splice(3, 1, { state: 5 + i, code: 1, name: snake.name });
        this.structureArray[4 + i].splice(2, 1, { state: 4, code: 0, name: snake.name });
        snake.head = { x: 4, y: 4 + i }; // 蛇头坐标
        snake.tail = { x: 2, y: 4 + i }; // 蛇尾坐标
        i = i + 4; // 每条蛇相隔三个格子
      }
    },
    initFrameTimer(interval) {
      let that = this;
      // 初始化定时器
      this.frameTimer = setInterval(() => {
        that.sendMyImage();
      }, interval);
    },
    givePower() {
      // 判断定时器是否还存在
      if(!this.hasMyTimer()){
        return;
      }
      // 清除旧的定时器
      this.clearMyTimer();
      // 加速
      this.initMyTimer(this.powerInterval);
    },
    cancelPower() {
      // 判断定时器是否还存在
      if(!this.hasMyTimer()){
        return;
      }
      // 清除旧的定时器
      this.clearMyTimer();
      // 取消加速
      this.initMyTimer(this.interval);
    },
    initMyTimer(interval) {
      // 初始化自己的定时器
      this.initTimer(this.snake, interval);
    },
    initTimer(thing,interval) {
      let that = this;
      // 初始化定时器
      thing.timer = setInterval(() => {
        let result = that.deathRule(thing);
        // 客户端只处理自己的死亡，对手的死亡由服务器发来的帧刷新
        if (result.alive) {
          // 未死则继续移动
          that.moveRule(thing);
        }else{
          // 死亡则发送通知
          that.sendDeadOrder(result.by);
        }
      }, interval);
    },
    initAllTimer() {
      // 初始化定时器
      for(let user in this.dragon){
        let snake = this.dragon[user]
        this.initTimer(snake,this.interval)
      }
    },
    hasMyTimer() {
      // 判断自己是否有定时器
      return this.hasTimer(this.snake.timer);
    },
    hasTimer(timer) {
      // 判断是否有定时器
      return timer != null;
    },
    clearFrameTimer() {
      // 清理帧同步定时器
      clearInterval(this.frameTimer);
      this.frameTimer = null;
    },
    clearMyTimer() {
      // 清理自己的定时器
      let me = this.snake;
      this.clearTimer(me);
      me.timer = null;

    },
    clearTimer(thing) {
      // 清理定时器
      clearInterval(thing.timer);
    },
    clearAllTimer(){
      // 清理所有定时器
      for(let user in this.dragon){
        let snake = this.dragon[user]
        this.clearTimer(snake)
      }
    },
    addKeyboardEvents() {
      // 添加全局键盘按下事件
      document.onkeydown = (event) => {
        console.log(event);
        switch (event.key) {
          case 'ArrowUp':
            this.changeMoveDirection(0)
            break;
          case 'ArrowRight':
            this.changeMoveDirection(1)
            break;
          case 'ArrowDown':
            this.changeMoveDirection(2)
            break;
          case 'ArrowLeft':
            this.changeMoveDirection(3)
            break;
        }
      };
    },
  },
  async mounted() {
    await this.initDragon(); //初始化角色
    await this.initGrid(); // 初始化网格
    this.initSnake(); // 初始化蛇
    this.initMyTimer(this.interval); // 初始化定时器
    // this.generateDust(); // 生成食物
    this.addKeyboardEvents(); // 添加键盘事件
    this.initFrameTimer(this.frameInterval);
  },
  unmounted() {
    this.clearMyTimer();
    this.clearFrameTimer();
  },
  computed: {
    /* longitudinal() {
      // 获取容器宽高比,计算纵坐标方格数
      let boxWidth = this.$refs.container.clientWidth;
      let boxHeight = this.$refs.container.clientHeight;
      let longitudinal = parseInt((boxHeight / boxWidth) * this.transverse);
      return longitudinal;
    }, */
    snake() {
      // 自己的小龙
      return this.dragon[this.me];
    },
    now() {
      // 获取当前时间的字符串
      let date = new Date();
      let yy = date.getFullYear();
      let mm = date.getMonth()+1;
      let dd = date.getDate();
      let hh = date.getHours();
      let mf = date.getMinutes()<10 ? '0'+date.getMinutes() : date.getMinutes();
      let ss = date.getSeconds()<10 ? '0'+date.getSeconds() : date.getSeconds();
      return yy+'-'+mm+'-'+dd+' '+hh+':'+mf+':'+ss;
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.tetris-wrapper {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background: #fff;
  overflow: auto;
}
.game-wrapper {
  position: absolute;
  top: 0.1rem;
  bottom: 0.1rem;
  left: 0.1rem;
  right: 0.1rem;
  display: flex;
  flex-direction: column;
  background-color: grey;
}
.block-group {
  flex: 1;
  position: relative;
  display: flex;
}
.block {
  flex: 1;
  border: 0.01rem solid #000;
}
.green {
  background-color: green;
}
.red {
  background-color: red;
}
.blue {
  background-color: blue;
}
.black {
  background-color: black;
}
.white {
  background-color: white;
}
.yellow {
  background-color: yellow;
}
.cyan {
  background-color: cyan;
}
.magenta {
  background-color: magenta;
}
.oxy {
  background-color: #e45f2e;
}
</style>
